using System;
using System.Collections.Concurrent;
using System.ComponentModel;
using JetBrains.Annotations;
using SF.Code.Extension.WinAPI;
using SG.Ling.AutoTranslator.Common;

namespace SG.Ling.AutoTranslator.App
{
    public enum Command : ushort
    {
        None = 0,
        Show,
        Hide,
        Start,
        Break,
        Close,
        Test,
        Translate,
        Speak,
    }

    public enum CommandGroup : ushort
    {
        [UsedImplicitly] None = 0,
        Application,
        Debug,
    }

    public static class CommandGroupEx
    {
        private static readonly ConcurrentDictionary<string, uint> WindowMessages = new ConcurrentDictionary<string, uint>();

        [Pure]
        public static IntPtr GetCompact(this CommandGroup group, Command command)
        {
            return new IntPtr((uint) group << 8 * sizeof (ushort) | (ushort) command);
        }

        public static uint GetWindowMessage(this CommandGroup group)
        {
            var key = Infrastructure.MainWindowName + "#" + @group;

            return WindowMessages.GetOrAdd(key, User32.RegisterWindowMessage);
        }

        public static Command ValidateCommand(this CommandGroup wantedGroup, WinProcMessageEventArgs args)
        {
            var msg = args.Message.Msg;
            var wParam = (uint)args.Message.WParam.ToInt32();

            if ((WindowMessage)msg == WindowMessage.WM_SETTEXT)
            {
                var actualGroup = (wParam >> 8 * sizeof(ushort)) & ushort.MaxValue;
                return (uint)wantedGroup != actualGroup ? Command.None : (Command)(wParam & ushort.MaxValue);
            }

            return wantedGroup.GetWindowMessage() != msg ? Command.None : (Command) wParam;
        }

        public static bool SendCommand(this CommandGroup group, Command command, IntPtr? handle = null, bool throwOnError = false, string args = null)
        {
            if (!handle.HasValue || handle.Value == IntPtr.Zero)
                handle = FindApplicationWindow();

            if (handle.Value == IntPtr.Zero)
                return false;

            try
            {
                var hresult = args != null
                                  ? User32.SendMessage(handle.Value, (uint)WindowMessage.WM_SETTEXT, @group.GetCompact(command), args)
                                  : User32.SendMessage(handle.Value, @group.GetWindowMessage(),
                                                       (IntPtr) (uint) command, IntPtr.Zero);

                if (hresult != IntPtr.Zero)
                    throw new Win32Exception(hresult.ToInt32());
            }
            catch (Exception)
            {
                if (throwOnError)
                    throw;
                return false;
            }

            return true;
        }

        public static IntPtr FindApplicationWindow()
        {
            return User32.FindWindow(null, Infrastructure.MainWindowName);
        }
    }
}